use actix_web::dev::Payload;
use actix_web::web::{BufMut, BytesMut};
use actix_web::{FromRequest, HttpRequest};
use futures_util::StreamExt;
use serde::de::DeserializeOwned;
use std::future::Future;
use std::pin::Pin;

use crate::error::{AppErr, ErrWrap};

pub const REQ_BODY_MAX: usize = 10 * 1024 * 1024;

const CONTENT_LENGTH: &'static str = "Content-Length";

pub struct BodyReq<T>(pub T);

async fn fetch_body<T: DeserializeOwned + 'static>(
    req: HttpRequest,
    mut payload: Payload,
) -> Result<BodyReq<T>, AppErr> {
    let cl: usize = req
        .headers()
        .get(CONTENT_LENGTH)
        .wrap()?
        .to_str()
        .wrap()?
        .parse()
        .wrap()?;
    if cl > REQ_BODY_MAX {
        return Err(AppErr::new_with_static("数据过大"));
    }
    let mut buf = BytesMut::with_capacity(cl);

    while let Some(item) = payload.next().await {
        let item = item.wrap()?;
        if buf.len() + item.len() >= REQ_BODY_MAX {
            return Err(AppErr::new_with_static("数据过大"));
        }
        buf.put_slice(&item);
    }

    let body: T = serde_cbor::from_slice(&buf).wrap()?;

    Ok(BodyReq(body))
}

impl<T: DeserializeOwned + 'static> FromRequest for BodyReq<T> {
    type Error = AppErr;
    type Future = Pin<Box<dyn Future<Output = Result<Self, AppErr>>>>;

    fn from_request(
        req: &actix_web::HttpRequest,
        payload: &mut actix_web::dev::Payload,
    ) -> Self::Future {
        let payload = payload.take();
        Box::pin(fetch_body(req.clone(), payload))
    }
}
